﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Security.Cryptography;
using System.Text;

namespace Galaktika.BI.Silverlight.ClientApp.Web.Runtime
{
    using Galaktika.BI.Extensibility.Dom;
    using Galaktika.BI.Extensibility.Dom.Dac;
    using Galaktika.BI.Silverlight.ClientApp.Web.DAL;
    using Galaktika.BI.Silverlight.ClientApp.Web.Runtime.DataAccess;
    using Galaktika.BI.Silverlight.ClientApp.Web.Runtime.Services;
    using Galaktika.BI.Runtime.Services;
    using System.Web.SessionState;
    using System.Runtime.Serialization;

    //[Serializable]
    public class DataBinder// : ISerializable
    {
        protected DataBinder(SerializationInfo info, StreamingContext context)
        {
        }

        public const string STORAGE_NAME = "_$DataBinder";
        const string DEFAULT_CONTEXT_NAME = "_$DefaultContext";

        #region Utils
        private static class Utils
        {
            public static bool AreEquals<T>(T[] ar1, T[] ar2)
            {
                if (ar1 == null && ar2 == null) return false;
                if (ar1 == null || ar2 == null) return false;
                if (ar1.Length != ar2.Length) return false;

                for (int i = 0; i < ar1.Length; i++)
                {
                    if (!ar1[i].Equals((ar2[i]))) return false;
                }

                return true;
            }
        }
        #endregion
        #region DataBindingContext
        [Serializable]
        private class DataBindingContext : IBindingContext
        {
            public DataBindingContext(DataBinder binder, VariablesContainer vars, string id)
            {
                this.Binder = binder;
                this.Variables = vars;
                this.Id = id;
                m_Managers = new Dictionary<IDataManager, IBindingManager>();
                HttpContext context = HttpContext.Current;
                if (context != null)
                {
                    foreach (var v in vars)
                    {
                        foreach (var pv in v.Value.PrincipalValues)
                        {
                            switch (pv.Type)
                            {
                                case DalPrincipalType.User:
                                    if (context.User.Identity.Name.Equals(pv.Name, StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        v.Value.CurrentValue = pv.Value;
                                    }
                                    break;
                                case DalPrincipalType.Role:
                                    if (context.User.IsInRole(pv.Name))
                                    {
                                        v.Value.CurrentValue = pv.Value;
                                    }
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }
            }

            public string Id { get; private set; }
            public DataBinder Binder { get; private set; }
            public VariablesContainer Variables { get; private set; }

            private Dictionary<IDataManager, IBindingManager> m_Managers;
            #region IBindingContext Members

            public IServiceProvider Services
            {
                get
                {
                    return this.Binder.Services;
                }
            }

            private bool m_IsDirty = true;
            public bool IsDirty
            {
                get
                {
                    return m_IsDirty;
                }
                set
                {
                    m_IsDirty = value;
                }
            }

            public void Reset()
            {
                foreach (IBindingManager manager in m_Managers.Values)
                {
                    manager.Reset();
                }
                if (m_UserData != null)
                {
                    m_UserData.Clear();
                }
            }

            public void Refresh()
            {
                foreach (IBindingManager manager in m_Managers.Values)
                {
                    manager.Refresh();
                }
            }

            public IBindingManager this[IDataManager dataManager]
            {
                get
                {
                    IBindingManager manager;
                    m_Managers.TryGetValue(dataManager, out manager);
                    if (manager == null)
                    {
                        manager = new DataBindingManager(this.Binder, this, dataManager);
                        m_Managers.Add(dataManager, manager);
                    }

                    return manager;
                }
            }

            private SafeDictionary<string, object> m_UserData;
            public IDictionary<string, object> UserData
            {
                get
                {
                    if (m_UserData == null)
                    {
                        m_UserData = new SafeDictionary<string, object>();
                    }

                    return m_UserData;
                }
            }

            #endregion
        }
        #endregion
        #region DataBindingManager
        private class DataBindingManager : IBindingManager
        {
            public DataBindingManager(DataBinder binder, IBindingContext context, IDataManager dataManager)
            {
                this.Binder = binder;
                m_DataManger = dataManager;
                m_Context = context;
            }

            public DataBinder Binder { get; private set; }

            #region IBindingManager Members

            private IBindingContext m_Context;
            public IBindingContext Context
            {
                get
                {
                    return m_Context;
                }
            }

            private IDataManager m_DataManger;
            public IDataManager DataManager
            {
                get
                {
                    return m_DataManger;
                }
            }

            private int m_Count = 0;
            public int Count
            {
                get
                {
                    return m_Count;
                }
            }

            private int[] m_Position = new int[] { 0 };
            public int[] Position
            {
                get
                {
                    return m_Position;
                }
                set
                {
                    if (!Utils.AreEquals<int>(m_Position, value))
                    {
                        m_Position = value;
                        m_Context.IsDirty = true;
                    }
                }
            }

            public void Reset()
            {
                //m_Context = null;
                m_BindObject = null;
                m_Position = new int[] { 0 };
            }

            public bool IsDataDirty
            {
                get
                {
                    return m_BindObject == null;
                }
            }

            public void CreateBindObject()
            {
                m_BindObject = m_DataManger[m_Context];
            }

            public void Refresh()
            {
                //if (m_Context.IsDirty)
                //{
                //    DataBinder.Current.Refresh(m_Context);
                //}
            }

            private object DoDrillDown(object obj, string[] path)
            {
                return this.DoDrillDown(obj, path, 0);
            }

            private object DoDrillDown(object obj, string[] path, int pathIndex)
            {
                if (pathIndex < path.Length)
                {
                    IEnumerable rows = this.GetEnumerable(obj);
                    if (rows != null)
                    {
                        int position = 0;
                        if (pathIndex == path.Length - 1)
                        {
                            m_Count = this.GetCount(rows);
                        }
                        foreach (object rowObj in rows)
                        {
                            if ((pathIndex == path.Length - 1 && position == this.Position[0]) ||
                                (pathIndex != path.Length - 1))
                            {
                                PropertyDescriptorCollection props = TypeDescriptor.GetProperties(rowObj);
                                int i = 0;
                                int reqIndex = this.GetAbsolutePosition(path[pathIndex]);
                                foreach (PropertyDescriptor prop in props)
                                {
                                    if (i == reqIndex || prop.Name == path[pathIndex])
                                    {
                                        return DoDrillDown(prop.GetValue(rowObj), path, ++pathIndex);
                                    }

                                    i++;
                                }
                            }
                            position++;
                        }
                    }
                }

                return obj;
            }

            private IEnumerable GetEnumerable(object obj)
            {
                IEnumerable res = null;
                if (obj is IListSource)
                {
                    res = ((IListSource)obj).GetList();
                }
                else
                {
                    if (obj is IEnumerable)
                    {
                        res = (IEnumerable)obj;
                    }
                }

                return res;
            }

            private int GetCount(IEnumerable enumetable)
            {
                if (enumetable is ICollection)
                {
                    return ((ICollection)enumetable).Count;
                }

                int i = 0;
                foreach (object obj in enumetable)
                {
                    i++;
                }

                return i;
            }

            private int GetAbsolutePosition(string path)
            {
                int res;
                if (int.TryParse(path, out res))
                {
                    return res;
                }

                return -1;
            }

            private object m_BindObject;
            protected object BindObject
            {
                get
                {
                    if (m_BindObject == null)
                    {
                        this.CreateBindObject();
                    }

                    return m_BindObject;
                }
            }

            public object this[string path]
            {
                get
                {
                    object obj = this.DoDrillDown(this.BindObject, new string[] { path });//.Split('.'));
                    return obj;
                }
            }

            #endregion
        }
        #endregion
        #region DataSourceManager
        private class DataSourceManager
        {
            SafeDictionary<string, IDataManager> m_Cache;
            public DataSourceManager(IEnumerable<DalDataSource> dataSources)
            {
                m_Cache = new SafeDictionary<string, IDataManager>();
                foreach (DalDataSource ds in dataSources)
                {
                    DalConnection conn = ds.Connection;
                    DalQuery query = ds.Query;
                    switch (conn.Provider)
                    {
                        case DataProvider.Odbc:
                            m_Cache.Add(ds.Name, new OdbcDataManager(conn.ConnectionString, query.Query));
                            break;
                        case DataProvider.OleDb:
                            m_Cache.Add(ds.Name, new OleDbDataManager(conn.ConnectionString, query.Query));
                            break;
                        case DataProvider.SqlServer:
                            m_Cache.Add(ds.Name, new SqlDataManager(conn.ConnectionString, query.Query));
                            break;
                        case DataProvider.Olap:
                            m_Cache.Add(ds.Name, new OlapDataManager(new Ranet.Olap.Core.ConnectionInfo(conn.Name, conn.ConnectionString), query.Query, query.UpdateScript));
                            break;
                        default:
                            break;
                    }
                }
            }

            public string GetContentType(string dataSource)
            {
                IDataManager dataManager;
                m_Cache.TryGetValue(dataSource, out dataManager);
                if (dataManager is OlapDataManager)
                {
                    return KnownContentTypes.MultidimData;
                }

                return KnownContentTypes.RelationalData;
            }

            public IDataManager this[string dataSourceName]
            {
                get
                {
                    return m_Cache[dataSourceName];
                }
            }

            public SafeDictionary<string, IDataManager> AsDictionary
            {
                get { return m_Cache; }
            }
        }
        #endregion
        #region NamedObjectCache
        private class NamedObjectCache
        {
            private SafeDictionary<string, BrickObject> m_Cache;
            public NamedObjectCache(DalRootContainer app)
            {
                m_Cache = new SafeDictionary<string, BrickObject>();
                foreach (DalDataSource ds in app.DataSources)
                {
                    if (m_Cache.ContainsKey(ds.Connection.Name)) continue;
                    m_Cache.Add(ds.Connection.Name, ds.Connection.ToBrick());
                }
            }

            public BrickObject this[string name]
            {
                get
                {
                    return m_Cache[name];
                }
            }
        }
        #endregion

        public static DataBinder Current
        {
            get
            {
                HttpContext context = HttpContext.Current;
                if (context != null)
                {
                    HttpSessionState session = context.Session;
                    if (session != null)
                    {
                        lock (session)
                        {
                            DataBinder binder = session[DataBinder.STORAGE_NAME] as DataBinder;
                            if (binder == null)
                            {
                                binder = new DataBinder(AppManager.Instance, AppManager.Services);
                                session[DataBinder.STORAGE_NAME] = binder;
                            }
                            return binder;
                        }
                    }
                }

                return null;
            }
        }

        DataSourceManager m_DataSources;
        NamedObjectCache m_NamedObjects;
        public DataBinder(DalRootContainer metadata, IServiceProvider services)
        {
            this.Metadata = metadata;
            this.Services = services;
            m_Contexts = new SafeDictionary<string, IBindingContext>();
            m_Contexts.Add(DEFAULT_CONTEXT_NAME, new DataBindingContext(this, new VariablesContainer(metadata.Variables), DEFAULT_CONTEXT_NAME));

            foreach (DalContext context in metadata.Contexts)
            {
                m_Contexts.Add(context.Name, new DataBindingContext(this, new VariablesContainer(metadata.Variables), context.Name));
            }

            m_DataSources = new DataSourceManager(metadata.DataSources);
            m_NamedObjects = new NamedObjectCache(metadata);
        }

        private IDictionary<string, IBindingContext> m_Contexts;
        public IBindingContext DefaultContext
        {
            get
            {
                return m_Contexts[DEFAULT_CONTEXT_NAME];
            }
        }

        public VariablesContainer DefaultVariables
        {
            get
            {
                return this.GetVariables(this.DefaultContext);
            }
        }

        public VariablesContainer GetVariables(string contextId)
        {
            return this.GetVariables(this.GetContext(contextId)); ;
        }

        public VariablesContainer GetVariables(IBindingContext context)
        {
            return ((DataBindingContext)context).Variables;
        }

        public DalRootContainer Metadata { get; private set; }
        public IServiceProvider Services { get; private set; }

        public void Reset(string contextID)
        {

            m_Contexts.Remove(contextID);
            m_DataSources = new DataSourceManager(Metadata.DataSources);
        }

        public void Refresh(IBindingContext context)
        {
            // TODO: refresh data managers
        }

        public BrickObject GetNamedObject(string name)
        {
            return m_NamedObjects[name];
        }

        public object CreateDataObject(string contextId, string dataSourceId)
        {
            IBindingContext context = this.GetOrCreateContext(contextId);
            IDataManager dataManager = m_DataSources[dataSourceId];
            return dataManager[context];
        }

        public string GetContentType(string dataSourceId)
        {
            return m_DataSources.GetContentType(dataSourceId);
        }

        public IBindingManager GetBindingManager(string contextId, string dataSourceId)
        {
            IBindingContext context = this.GetContext(contextId);
            return context[m_DataSources[dataSourceId]];
        }

        public IBindingContext GetContext(string contextId)
        {
            return this.GetOrCreateContext(contextId);
        }

        private IBindingContext GetOrCreateContext(string contextId)
        {
            if (string.IsNullOrEmpty(contextId))
            {
                return this.DefaultContext;
            }
            IBindingContext context = null;
            lock (m_Contexts)
            {
                m_Contexts.TryGetValue(contextId, out context);
                if (context == null)
                {
                    context = m_Contexts[contextId] = new DataBindingContext(this, new VariablesContainer(), contextId);
                }
            }


            return context;
        }

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            
        }

        #endregion

        internal string GetHashableValue()
        {
            string result = "";
            foreach (string contextId in m_Contexts.Keys)
            {
                result += String.Format("contextId=[{0}][", contextId);
                VariablesContainer vars = this.GetVariables(contextId);
                foreach (KeyValuePair<string, DalVariable> kvp in vars)
                {
                    result += String.Format("{0} {1}={2}", kvp.Key, kvp.Value.Name, kvp.Value.CurrentValue);
                }
                result += "]";
            }
            return result;
        }

        /// <summary>
        /// Получить хэш статуса (SHA1).
        /// </summary>
        /// <param name="contextId"></param>
        /// <returns></returns>
        internal string GetStatusHash(string contextId)
        {
            string input;
            // формируем строку для вычисления хэша
            input = String.Format("contextId={0} [", contextId);
            // добавляем пары имя переменной - значение.
            VariablesContainer vars = this.GetVariables(contextId);
            foreach (KeyValuePair<string, DalVariable> kvp in vars)
            {
                input += String.Format("{0}={1} ", kvp.Value.Name, kvp.Value.CurrentValue);
            }
            input += "]";
            // добавляем менеджеры olap
            foreach (KeyValuePair<string, IDataManager> item in m_DataSources.AsDictionary)
            {
                if (item.Value is OlapDataManager)
                {
                    OlapDataManager manager = (OlapDataManager) item.Value;
                    input += String.Format(" {0}={1}", manager.Name, manager.History.Size);
                }
            }
            // вычисляем хэш
            SHA1Managed sha1 = new SHA1Managed();
            string hash = BitConverter.ToString(sha1.ComputeHash(Encoding.Unicode.GetBytes(input))).Replace("-", "");

            return hash;
        }
    }
}
